﻿HmJS.$Import('core.element');

HmJS.register('core.fx', function ($ns) {

	var document = window.document;

	// #region -- Fx --

	/*
	---

	name: Fx

	description: Contains the basic animation logic to be extended by all other Fx Classes.

	license: MIT-style license.

	requires: [Chain, Events, Options]

	provides: Fx

	源码分析: 苦苦的苦瓜(http://hmking.blog.51cto.com)
	...
	*/

	(function () {

		/**
		* @Fx: 本类一般不独立使用,它用来提供作为Fx系的类的基础功能类.所有其他的Fx系列的类都继承本类.
		**/
		var Fx = this.Fx = new Class({

			Implements: [Chain, Events, Options],

			// #region - constructor -

			/**
			* @Events:
			* @event start - (function) 特效开始执行时触发
			* @event cancel - (function) 手动停止特效执行时触发
			* @event complete - (function) 特效执行完成后触发
			* @event chainComplete - (function) 当使用link可选项为'chain'时, 该事件在特效链执行完后触发
			* @event stop - (function) 特效执行完成前，执行stop方法时触发
			**/

			/**
			* @Optoins: 
			* @option fps - (number: 默认为 60) 动画特效的秒帧数
			* @option unit - (string: 默认为 false) 计量单位(如: 'px', 'em', 或 '%').
			* @option duration - (number: 默认为 500) 可以让你定义这个动画的持续时间。持续时间和速度是不一样的，因此如果你想让一个对象在一秒内移动100个像素，
			*		那么它将比一个每秒移动1000个像素的对象要慢。你可以输入一个数字（以毫秒为单位）. 也可使用以下预定义字符串:
			*		'short' - 250ms
			*		'normal' - 500ms
			*		'long' - 1000ms
			* @option frames - (number) 设定动画特效执行的总帧数，默认为null自动匹配
			* @option frameSkip - (boolean: 默认为true) 设定动画特效当一帧执行的时间大于每帧之间的时间间隔，是否跳过这段时间所要执行的帧
			* @option link - (string: 默认为 ignore) 可为: 'ignore', 'cancel' 或 'chain'
			*		'ignore' - 当特效正在执行之中时,再次调用特效开始的方法将被忽略(和可选项'wait'为true时同义)
			*		'cancel' - 当特效正在执行之中时,再次调用特效开始的方法将立即取消当前执行的特效,开始执行新的特效
			*		'chain' - 当特效正在执行之中时,再次调用特效开始的方法将会把新的特效链接在当前执行的特效之后,依次执行各个特效
			* @option transition - (function: 默认为 Fx.Transitions.Sine.easeInOut) 特效的变换方程, 详见Fx.Transitions. 也可以使用如下格式的字符串:
			*		transition[:in][:out] - 例如: 'linear', 'quad:in', 'back:in', 'bounce:out', 'elastic:out', 'sine:in:out'
			**/
			options: {
				/*
				onStart: nil,
				onCancel: nil,
				onComplete: nil,
				*/
				fps: 60,
				unit: false,
				duration: 500,
				frames: null,
				frameSkip: true,
				link: 'ignore'
			},

			initialize: function (options) {
				this.subject = this.subject || this;
				this.setOptions(options);
			},

			// #endregion

			/**
			* @method: getTransition
			* @returns: (function) - 特效的变换方程
			* @description: 取得动画特效所要执行的特效方程
			**/
			getTransition: function () {
				return function (p) {
					return -(Math.cos(Math.PI * p) - 1) / 2;
				};
			},

			/**
			* @method: step
			* @param now - (mixed) 特效值
			* @returns: (function) - 特效的变换方程
			* @description: 动画特效每一步执行的操作
			**/
			step: function (now) {
				if (this.options.frameSkip) {
					// 先取得当前时间减去上一帧执行时的时间，得到两帧之间的时间间隔，计算这段时间内按正常的帧间隔时间能执行的帧的数量
					var diff = (this.time != null) ? (now - this.time) : 0,
							frames = diff / this.frameInterval;
					// 存储当前帧执行时的时间
					this.time = now;
					// 执行的帧数累加
					this.frame += frames;
				} else {
					this.frame++;
				}

				// 判断当前帧是否为动画特效的最后一帧
				if (this.frame < this.frames) {
					// 通过特效方程计算动画特效运行当前帧所要变化的比例因子
					var delta = this.transition(this.frame / this.frames);
					this.set(this.compute(this.from, this.to, delta));
				} else {
					// 动画特效执行完毕
					this.frame = this.frames;
					this.set(this.compute(this.from, this.to, 1));
					this.stop();
				}
			},

			/**
			* @method: set
			* @param value - (mixed) 特效值
			* @description: 用于设置特效值.该方法在特效变换过程中每个'步进'都会调用; 也可以手工调用，留作派生类实现
			**/
			set: function (now) {
				return now;
			},

			/**
			* @method: compute
			* @param from - (mixed) 特效的起始值
			* @param to - (mixed) 特效的结束值
			* @param delta - (mixed) 特效变化所需要的比例因子
			* @description: 根据初始值，结束值和比例因子求目标值
			**/
			compute: function (from, to, delta) {
				return Fx.compute(from, to, delta);
			},

			/**
			* @method: check
			* @parameters - 与start方法参数一致
			* @returns: (boolean) - 如果start方法可以继续执行, 则返回 true ; 否则返回 false
			* @description: 判断当特效正在执行之中时，再次调用特效开始的方法(start)是否继续可以执行start方法
			**/
			check: function () {
				// 如果特效没有运行，返回true
				if (!this.isRunning()) { return true; }
				switch (this.options.link) {
					case 'cancel': // 不等待正在运行的特效，直接取消并重新开始
						this.cancel();
						return true;

					case 'chain': // 等待当前特效运行结束后再继续运行新特效
						this.chain(this.caller.pass(arguments, this));
						return false;
				}
				return false;
			},

			/**
			* @method: start
			* @param from - (mixed) 特效的起始值. 如果只给出一个参数,则本值将作为结束值
			* @param to - (mixed, 可选) 特效的结束值
			* @returns: (object) - 当前的Fx实例
			* @description: 开始执行特效变换(并触发'start'事件)
			**/
			start: function (from, to) {
				// 检测start方法是否可以继续执行
				if (!this.check(from, to)) { return this; }

				/**
				# 苦苦的苦瓜
				# 2011-09-25
				# 将用局部变量_options代替this.options
				**/
				var _options = this.options;

				this.from = from;
				this.to = to;
				this.frame = (_options.frameSkip) ? 0 : -1;
				this.time = null;
				// 取得特效执行的变换方程
				this.transition = this.getTransition();
				var frames = _options.frames,
						fps = _options.fps,
						duration = _options.duration;
				// 可选参数duration既可以数字类型，也可以为字符串类型
				this.duration = Fx.Durations[duration] || duration.toInt();
				// 取得动画特效每帧之间的时间间隔，毫秒为单位
				this.frameInterval = 1000 / fps;
				// 计算动画特效执行的总帧数
				this.frames = frames || Math.round(this.duration / this.frameInterval);
				// 触发'start'事件
				this.fireEvent('start', this.subject);
				pushInstance.call(this, fps);
				return this;
			},

			/**
			* @method: stop
			* @returns: (object) - 当前的Fx实例
			* @description: 停止一个特效的执行
			**/
			stop: function () {
				if (this.isRunning()) {
					this.time = null;
					pullInstance.call(this, this.options.fps);
					if (this.frames == this.frame) {
						this.fireEvent('complete', this.subject);
						if (!this.callChain()) {
							this.fireEvent('chainComplete', this.subject);
						}
					} else {
						this.fireEvent('stop', this.subject);
					}
				}
				return this;
			},

			/**
			* @method: cancel
			* @returns: (object) - 当前的Fx实例
			* @description: 取消一个特效的执行(并触发'cancel'事件)
			**/
			cancel: function () {
				if (this.isRunning()) {
					this.time = null;
					pullInstance.call(this, this.options.fps);
					this.frame = this.frames;
					this.fireEvent('cancel', this.subject).clearChain();
				}
				return this;
			},

			/**
			* @method: pause
			* @returns: (object) - 当前的Fx实例
			* @description: 暂停当前执行的特效
			**/
			pause: function () {
				if (this.isRunning()) {
					this.time = null;
					pullInstance.call(this, this.options.fps);
				}
				return this;
			},

			/**
			* @method: pause
			* @return: (object) - 当前的Fx实例
			* @description: 恢复执行暂停中的特效
			* @remark: 只有对暂停中的特效执行本方法才有效果, 否则将忽略.
			**/
			resume: function () {
				if ((this.frame < this.frames) && !this.isRunning()) {
					pushInstance.call(this, this.options.fps);
				}
				return this;
			},

			/**
			* @method: isRunning
			* @return: (boolean) 特效运行状态
			* @description: 检测特效是否正在运行
			**/
			isRunning: function () {
				var list = instances[this.options.fps];
				return list && list.contains(this);
			}

		});

		Fx.compute = function (from, to, delta) {
			return (to - from) * delta + from;
		};

		// 预置特效间隔毫秒数，当可选参数duration为字符串时调用Fx.Durations对象得到特效间隔毫秒数
		Fx.Durations = { 'short': 250, 'normal': 500, 'long': 1000 };

		// global timers
		// instances对象字面量缓存所有的fx实例对象，它的所有键值对中的键就是对应一个fps值，值为一个包含设置了相同fps的动画特效实例的数组
		// timers对象缓存setInterval()方法返回的ID值
		var instances = {},
				timers = {};

		/**
		* @method: loop
		* @description: 遍历动画特效实例数组，执行动画特效
		**/
		var loop = function () {
			var now = Date.now();
			/**
			# 苦苦的苦瓜 
			# 2011-10-12
			# for循环改为while循环
			# for (var i = this.length; i--; ) {
			**/
			var i = this.length;
			while (i--) {
				var instance = this[i];
				if (instance) { instance.step(now); }
			}
		};

		/**
		* @method: pushInstance
		* @description: 遍历动画特效实例数组，执行动画特效
		**/
		var pushInstance = function (fps) {
			// 取得缓存的与参数fps对应的动画特效数组，如果没有则instances对象新建一个键值存储这个数组
			var list = instances[fps] || (instances[fps] = []);
			// 缓存fx实例对象
			list.push(this);
			if (!timers[fps]) {
				// 设置定时器
				timers[fps] = loop.periodical(Math.round(1000 / fps), list);
			}
		};

		/**
		* @method: pullInstance
		* @param from - (number) 要停止的动画特效实例的秒帧数
		* @description: 停止运行一个动画特效实例
		**/
		var pullInstance = function (fps) {
			// 取得缓存的与参数fps对应的动画特效数组
			var list = instances[fps];
			if (list) {
				// 从数组中删除运行pullInstance函数的fx实例对象
				list.erase(this);
				if (!list.length && timers[fps]) {
					// 如果数组为空，则删除instances对象中的这个数组
					delete instances[fps];
					// 清除定时器
					timers[fps] = clearInterval(timers[fps]);
				}
			}
		};

	})();

	// #endregion

	// #region -- Fx.CSS --

	/*
	---

	name: Fx.CSS

	description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.

	license: MIT-style license.

	requires: [Fx, Element.Style]

	provides: Fx.CSS

	源码分析: 苦苦的苦瓜(http://hmking.blog.51cto.com)

	...
	*/

	/**
	* @Fx.CSS: 跟CSS有关的动画的基类，这里的动画，主要是从一个开始值到结束值的变化效果
	**/
	Fx.CSS = new Class({

		// 继承自Fx
		Extends: Fx,

		// prepares the base from/to object
		/**
		* @method: prepare
		* @param element - (object) 特效作用的元素对象
		* @param property - (string) CSS属性
		* @param values - (mixed) 包含开始值和结束值的数组或一个单值(结束值)
		* @returns: (object) - 包含from和to两个键值的对象字面量
		* @description: 动画的开始和结束值的前期处理
		* @notes: 此时from和to两个键的值为数组类型
		**/
		prepare: function (element, property, values) {
			// 把变量values数组化，因为values可能传一个单值，也可能是一个数组
			values = Array.from(values);
			// 取特效的起始值和结束值，如果如果只传了一个值，则本值将作为结束值，CSS属性的当前值为特效起始值
			if (values[1] == null) {
				values[1] = values[0];
				values[0] = element.getStyle(property);
			}
			// 将数组中的项使用parse方法解释
			var parsed = values.map(this.parse);
			// 返回from和to两个键值的对象字面量
			return { from: parsed[0], to: parsed[1] };
		},

		//parses a value into an array
		/**
		* @method: parse
		* @param value - (mixed) CSS属性值
		* @returns: (array) - 数组项值为包含value和parser两个键值的对象字面量，存储解释过的CSS属性值和包含针对此属性值的解释器
		* @description: 解析一个CSS属性值为一个数组
		**/
		parse: function (value) {
			// 使用lambad表达式，将value函数化之后再执行，这样的好处是使传的值可以是function，也可以是固定值
			value = Function.from(value)();
			// 数组化，如果是字符串类型，使用空格分隔成数组
			value = (typeof value == 'string') ? value.split(' ') : Array.from(value);
			// 对数组逐项处理
			return value.map(function (val) {
				// 转为字符类型
				val = String(val);
				var found = false;
				Object.each(Fx.CSS.Parsers, function (parser, key) {
					// 第一项时这里为false继续执行下面，找到合适的解释器后found判断不再为false，避免重复解释
					if (found) { return; }
					// 尝试使用解释器解释值
					var parsed = parser.parse(val);
					// 如果解释成功，记录解释后的值和使用的解释器(因为还要使用解释器的compute和serve方法)
					if (parsed || parsed === 0) {
						found = {
							value: parsed,
							parser: parser
						};
					}
				});
				// 默认使用字符串值的解释器
				found = found || {
					value: val,
					parser: Fx.CSS.Parsers.String
				};
				return found;
			});
		},

		// computes by a from and to prepared objects, using their parsers.
		/**
		* @method: compute
		* @param from - (array) 解释过的CSS属性的起始值的数组
		* @param to - (array) 解释过的CSS属性的结束值的数组
		* @param delta - (mixed) 特效变化所需要的比例因子
		* @returns: (array) 包含计算过的特效当前CSS属性值信息的一个数组
		* @description: 根据初始值，结束值和比例因子求目标值
		**/
		compute: function (from, to, delta) {
			var computed = [];
			// 取数项小的遍历
			(Math.min(from.length, to.length)).times(function (i) {
				// 返回计算过的值和使用的解释器
				computed.push({
					value: from[i].parser.compute(from[i].value, to[i].value, delta),
					parser: from[i].parser
				});
			});
			// 为typeOf提供精准类型值
			computed.$family = Function.from('fx:css:value');
			return computed;
		},

		// serves the value as settable
		/**
		* @method: serve
		* @param value - (mixed) CSS属性目标值，此参数可以是一个解释过的CSS属性值数组，也可以为一个CSS属性值
		* @param unit - (string 默认为 false) 计量单位(如: 'px', 'em', 或 '%').
		* @returns: (array) 包含计算过的特效当前CSS属性值信息的一个数组
		* @description: 对计算过的CSS属性值数组对象做最后的包装处理，使其可应用于Element.setStyle方法
		**/
		serve: function (value, unit) {
			// 如果值未经解释，需要先解释(比如单独调用set方法)
			if (typeOf(value) != 'fx:css:value') {
				value = this.parse(value);
			}
			var returned = [];
			value.each(function (bit) {
				// 得到最终的使用值
				returned = returned.concat(bit.parser.serve(bit.value, unit));
			});
			return returned;
		},

		// renders the change to an element
		// 因为类本身是跟CSS有类，所以最终将计算出的数组通过setStyle反映到element的相应CSS属性上
		render: function (element, property, value, unit) {
			element.setStyle(property, this.serve(value, unit));
		},

		// searches inside the page css to find the values for a selector
		// 从当前页面的样式中查找指定选择符的样式设置
		search: function (selector) {
			// 模拟缓存，先从临时对象中找相应键值，提高效率
			if (Fx.CSS.Cache[selector]) { return Fx.CSS.Cache[selector]; }
			var to = {},
					selectorTest = new RegExp('^' + selector.escapeRegExp() + '$');
			// 遍历当前页面的样式表
			Array.each(document.styleSheets, function (sheet, j) {
				var href = sheet.href;
				// 忽略跨域的外链样式表
				if (href && href.contains('://') && !href.contains(document.domain)) {
					return;
				}
				// 样式规则集
				var rules = sheet.rules || sheet.cssRules;
				// 遍历每条规则
				Array.each(rules, function (rule, i) {
					if (!rule.style) { return; }
					// 选择符(类型选择符的话会转为小写)
					var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function (m) {
						return m.toLowerCase();
					}) : null;
					// 匹配指定的样式选择符
					if (!selectorText || !selectorTest.test(selectorText)) { return; }
					// 样式值分析
					Object.each(Element.Styles, function (value, style) {
						// 无值
						if (!rule.style[style] || Element.ShortStyles[style]) { return; }
						// 转为字符串
						value = String(rule.style[style]);
						// 颜色值处理
						to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value;
					});
				});
			});
			// 缓存
			return Fx.CSS.Cache[selector] = to;
		}

	});

	Fx.CSS.Cache = {};

	// #region - 解释器 -

	// CSS中几种值类型的解释器，每个解释器必须实现parse/compute/serve三个接口
	Fx.CSS.Parsers = {

		// 对颜色的解释处理
		Color: {

			parse: function (value) {
				// 如果是十六进制的颜色表示，处理成RGB数组
				if (value.match(/^#[0-9a-f]{3,6}$/i)) {
					return value.hexToRgb(true);
				}
				// 如果是RGB的颜色显示，正则匹配出RGB数组，不匹配返回flase，以便引擎调用其它解释器解释
				return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
			},

			compute: function (from, to, delta) {
				// 对R、G和B分别计算目标值
				return from.map(function (value, i) {
					// 可以看到仍然使用静态的compute方法
					return Math.round(Fx.compute(from[i], to[i], delta));
				});
			},

			serve: function (value) {
				// 将R、G、B都转成数值型
				return value.map(Number);
			}

		},

		// 数值类型的解释处理
		Number: {

			// 转为浮点数
			parse: parseFloat,

			// 跟Fx中的算法一样
			compute: Fx.compute,

			serve: function (value, unit) {
				// 加上单位，比如px，pt之类
				return (unit) ? value + unit : value;
			}

		},

		// 对字符类型的解释处理
		String: {

			// 解释器返回false，相当于parse : function(){return false;}
			parse: Function.from(false),

			// compute方法执行时返回第2个参数
			compute: function (zero, one) {
				return one;
			},

			// serve方法执行时返回第1个参数
			serve: function (zero) {
				return zero;
			}

		}

	};

	// #endregion

	// #endregion

	// #region -- Fx.Tween --

	/*
	---

	name: Fx.Tween

	description: Formerly Fx.Style, effect to transition any CSS property for an element.

	license: MIT-style license.

	requires: Fx.CSS

	provides: [Fx.Tween, Element.fade, Element.highlight]

	源码分析: 苦苦的苦瓜(http://hmking.blog.51cto.com)

	...
	*/

	// #region - Fx.Tween -

	/**
	* @Fx.Tween: 对元素单个样式属性执行一个特效
	**/
	Fx.Tween = new Class({

		// 继承自Fx.CSS
		Extends: Fx.CSS,

		/**
		* @method: initialize
		* @param element - (mixed) 元素的id或引用
		* @param options - (object, 可选) 类可用的所有可选项, 以及如下的可选项:
		*		property - (string, 默认为null) 变换的目标CSS属性,例如:'width', 'color', 'font-size', 'border'等. 
		*								如果在此省略该可选项, 则在执行start或set方法时,需要在方法的第一个参数上指定一个CSS属性.
		* @description: 构造函数，提供将元素的一个CSS属性值从一个值向另一个值进行转化的功能
		* @notes:
		*		任何可以用Element:setStyle设置的CSS属性都可以用于Fx.Tween
		*		如果不是可计算型的CSS属性, 如border-style 或 background-image等, 则只是简单的设置它的值
		*		如果使用了property可选项, 则在调用start和set方法时就不用再指定CSS属性
		**/
		initialize: function (element, options) {
			// element属性存储特效所作用的元素对象
			this.element = this.subject = document.id(element);
			// 调用父类的同名方法
			this.parent(options);
		},

		/**
		* @method: set
		* @param property - (string) css属性(如果在构造函数中设置了property可选项, 则该处可以省略)
		* @param now - (mixed) css属性值
		* @description: 将元素的指定CSS属性值立即设置为指定的值
		* @returns: (object) 主调Fx.Tween实例
		* @notes:
		*		如果使用了property可选项, 或者在start方法中指定了CSS属性参数，则在调用set方法时就不用指定CSS属性参数
		**/
		set: function (property, now) {
			// 如果只提供一个参数
			if (arguments.length == 1) {
				// 将此参数作为目标值
				now = property;
				// 取CSS属性，首先取Fx实例的property属性存储的CSS属性名
				property = this.property || this.options.property;
			}
			// 最终渲染效果
			this.render(this.element, property, now, this.options.unit);
			return this;
		},

		/**
		* @method: start
		* @param property - (string)  要进行变换的css属性(如果在构造函数中设置了property可选项, 则该处可以省略)
		* @param from - (mixed) CSS属性起始值。如果整个方法只给出一个参数，则该值作为CSS属性的结束值
		* @param to - (mixed, 可选) CSS属性结束值
		* @description: 将元素的CSS属性值过度到指定值
		* @notes:
		*		如果整个方法只给出一个参数,则该值作为CSS属性的结束值, 起始值则从元素的当前状态计算而来
		*		当变换颜色类的CSS属性时, 既可使用RGB格式也可以使用十六进制格式
		*		如果在构造函数中设置了property可选项, 则在调用start方法时就不用指定CSS属性参数
		**/
		start: function (property, from, to) {
			// 检查当前特效运行状态，决定是否运行新特效
			if (!this.check(property, from, to)) { return this; }
			// 将参数降维
			var args = Array.flatten(arguments);
			// 取CSS属性，首先判断有没有设置property可选项
			this.property = this.options.property || args.shift();
			// 调用父类Fx.CSS的prepare方法解释参数，得到from和to值
			var parsed = this.prepare(this.element, this.property, args);
			// 调用Fx基类的同名方法，开始执行特效
			return this.parent(parsed.from, parsed.to);
		}

	});

	// #endregion

	// #region - Element.Properties.tween -

	/**
	* @element property: tween
	* @description: 用于设置或获取元素上的Fx.Tween实例，实现单件模式
	* @notes:
	*		01、When initializing the Element's tween instance with Element:set, the property to tween SHOULD NOT be passed.
	*				当使用Element:set方法来设置元素的tween时, 则要进行tween的css属性<不需要>传入
	*		02、The property must be specified when using Element:get to retrieve the actual Fx.Tween instance, and in calls to Element:tween
	*				当使用Element:get方法来获取元素的Fx.Tween实例时, 则可选项中的property必须指定
	*		03、When options are passed to the setter, the instance will be reset.
	*				当使用setter方法设置可选参数时，Fx.Tween实例对象会被重置
	*		04、As with the other Element shortcuts, the difference between a setter and a getter is that the getter returns the instance, while the setter returns the element (for chaining and initialization).
	*				调用get方法获取tween返回的是Fx.Tween的实例, 而调用set返回的是主调元素
	*		05、当使用get方法时, 如果元素上还不存在tween, 则会根据给出的可选项新建一个实例设置到元素上
	**/
	Element.Properties.tween = {

		// setter设置Fx.Tween对象参数
		set: function (options) {
			// 取得Fx.Tween实例，取消执行中的特效，然后再设置可选参数
			this.get('tween').cancel().setOptions(options);
			return this;
		},

		get: function () {
			// 先从临时对象读取，看有没缓存到Fx.Tween实例
			var tween = this.retrieve('tween');
			if (!tween) {
				// 保存Fx.Tween实例
				tween = new Fx.Tween(this, { link: 'cancel' });
				this.store('tween', tween);
			}
			return tween;
		}

	};

	// #endregion

	// #region - Element Methods -

	// 元素进行特效变换的快捷方法
	Element.implement({

		/**
		* @element method: tween
		* @returns: (element) 返回主调元素
		* @notes: 参照Fx.Tween.start方法
		**/
		tween: function (property, from, to) {
			this.get('tween').start(property, from, to);
			return this;
		},

		/**
		* @element method: fade
		* @param how - (mixed, 可选: 默认为'toggle') 代表不透明度的数值或字符串. 可为如下值:
		*		'in' - opacity为100%
		*		'out' - opacity为0%
		*		'show' - opacity立即设置为100%
		*		'hide' - opacity立即设置为0%
		*		'toggle' - 如果元素当前为可见状态, 则将元素淡出; 相反, 则将元素淡入
		*		(number) - 0~1之间的浮点数. 将代入淡出到该值.
		* @returns: (element) 返回主调元素
		* @description: 对opacity样式属性进行tween特效变换的快捷方法，实现的深入浅出效果
		**/
		fade: function (how) {
			// 取得主调元素的Fx.Tween实例
			var fade = this.get('tween'),
					method, to, toggle;
			if (how == null) { how = 'toggle'; }
			// 几种淡入淡出的方式
			switch (how) {
				case 'in': // 淡入
					method = 'start';
					to = 1;
					break;

				case 'out': // 淡出
					method = 'start';
					to = 0;
					break;

				case 'show': // 显示
					method = 'set';
					to = 1;
					break;

				case 'hide': // 隐藏
					method = 'set';
					to = 0;
					break;

				case 'toggle': // 开关
					// 获取标记变量, 第二个参数用于默认值
					var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1);
					method = 'start';
					// 根据标记状态控制淡入还是淡出
					to = flag ? 0 : 1;
					// 将标记取反保存
					this.store('fade:flag', !flag);
					toggle = true;
					break;

				default:
					method = 'start';
					to = how;
			}
			// 如果没有使用开关方式，删除临时标记，避免在使用toggle之后又使用其它方式，导致toggle响应错误
			if (!toggle) { this.eliminate('fade:flag'); }
			// 根据指定的淡入淡出方式执行特效实例相应的方法
			fade[method]('opacity', to);
			// 根据opacity样式属性结束值是否为零来设置主调元素隐藏还是显示
			if (method == 'set' || to != 0) {
				this.setStyle('visibility', to == 0 ? 'hidden' : 'visible');
			} else fade.chain(function () {
				this.element.setStyle('visibility', 'hidden');
			});
			return this;
		},

		/**
		* @element method: highlight
		* @param start - (string, 可选: 默认为'#ff8') 高亮色
		* @param end - (string, 可选: 默认为元素初始的background-color值) 高亮效果结束后的元素背景颜色
		* @returns: (element) 返回主调元素
		* @description: 对background-color样式属性进行tween特效变换的快捷方法.(即背景高亮特效, 将背景颜色迅速设置为指定颜色，随后返回到初始的背景颜色)
		* @notes: 如果未给元素指定背景色, 或指定成了'transparent', 则end值默认为白色
		**/
		highlight: function (start, end) {
			// end为动画结束后使用的背景色，通常是原来恢复原来的颜色
			if (!end) {
				// 临时对象取值
				end = this.retrieve('highlight:original', this.getStyle('background-color'));
				// 透明的话按白色处理
				end = (end == 'transparent') ? '#fff' : end;
			}
			// 获取主调元素的Fx.Tween实例
			var tween = this.get('tween');
			// 开始执行
			tween.start('background-color', start || '#ffff88', end).chain(function () {
				// 动画结束恢复原来颜色
				this.setStyle('background-color', this.retrieve('highlight:original'));
				// 链式执行
				tween.callChain();
			} .bind(this));
			return this;
		}

	});

	// #endregion

	// #endregion

	// #region -- Fx.Morph --

	/*
	---

	name: Fx.Morph

	description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.

	license: MIT-style license.

	requires: Fx.CSS

	provides: Fx.Morph

	源码分析: 苦苦的苦瓜(http://hmking.blog.51cto.com)

	...
	*/

	// #region - Fx.Morph -

	/**
	* @Fx.Morph: 提供一次对多个CSS属性进行动画特效变换的功能，所以本类的属性为多值集合
	**/
	Fx.Morph = new Class({

		// 继承自Fx.CSS
		Extends: Fx.CSS,

		/**
		* @method: initialize
		* @param element - (mixed) 元素的id或引用
		* @param options - (object, 可选) Fx类中提供的可选项
		* @description: 构造函数，提供将多个元素的CSS属性值从一个值向另一个值进行转化的功能
		**/
		initialize: function (element, options) {
			// element属性存储特效所作用的元素对象
			this.element = this.subject = document.id(element);
			// 调用父类的同名方法
			this.parent(options);
		},

		/**
		* @method: set
		* @param now - (mixed) 可以是包含CSS属性键值对的对象, 或是一个CSS选择器(必须在页面中已定义).如果对CSS属性只给出一个值,则变换的时候将把元素当前的属性值作为起始值.
		*		{
		*			'height': 200,
		*			'width': 200,
		*			'background-color': '#f00',
		*			'opacity': 0
		*		}
		* @returns: (object) 主调Fx.Morph实例
		* @description: 将元素的指定CSS属性值立即设为指定值
		**/
		set: function (now) {
			// 如果参数是字符串类型，表示为CSS选择符，从当页页面的样式规则中查找指定的规则
			if (typeof now == 'string') {
				now = this.search(now);
			}
			// 分别设置每一项样式值
			for (var p in now) {
				this.render(this.element, p, now[p], this.options.unit);
			}
			return this;
		},

		/**
		* @method: compute
		* @param from - (object) 解释过的各项样式属性的起始值的对象
		* @param to - (object) 解释过的各项样式属性的结束值的对象
		* @param delta - (mixed) 特效变化所需要的比例因子
		* @returns: (array) 包含计算过的各项样式属性当前值信息的一个数组
		* @description: 根据各项样式属性初始值，结束值和特效比例因子计算各项样式属性的当前值
		**/
		compute: function (from, to, delta) {
			var now = {};
			for (var p in from) {
				// 对每一项样式属性，调用Fx.CSS类的同名方法计算
				now[p] = this.parent(from[p], to[p], delta);
			}
			return now;
		},

		/**
		* @method: start
		* @param roperties - (mixed) 可以是包含CSS属性键值对的对象, 或是一个CSS选择器(必须在页面中已定义).如果对CSS属性只给出一个值，则变换的时候将把元素当前的属性值作为起始值.
		*		{
		*			'height': [10, 100],
		*			'width': [900, 300],
		*			'opacity': 0,
		*			'background-color': '#00f'
		*		}
		* @returns: (object) - 主调Fx.Morph实例
		* @description: 串联执行多个CSS属性的变换(并触发'start'事件)
		* @notes:
		*		如果传入一个CSS选择器, 则该选择器必须在页面存在相应匹配的样式
		*		不支持多选择器(逗号分隔的多个选择器)
		*		@import'ed CSS rules will not be available for Morph calls. All CSS selectors must be present in CSS directly loaded into the page.
		**/
		start: function (properties) {
			// 检查当前特效运行状态，决定是否运行新特效
			if (!this.check(properties)) { return this; }
			// 如果提供properties参数类型为字符串，表明指定的是CSS选择符名，需要从当前页的样式规则中查找各项属性
			if (typeof properties == 'string') {
				properties = this.search(properties);
			}
			var from = {},
					to = {};
			// 对每项CSS属性值计算解释后的值，此时的from和to对象每个键值皆为一个数组
			for (var p in properties) {
				var parsed = this.prepare(this.element, p, properties[p]);
				from[p] = parsed.from;
				to[p] = parsed.to;
			}
			// 调用Fx类的构造函数
			return this.parent(from, to);
		}

	});

	// #endregion

	// #region - Element.Properties.morph -

	/**
	* @element property: morph
	* @description: 用于设置或获取元素上的Fx.Morph实例，实现单件模式
	**/
	Element.Properties.morph = {

		// setter设置Fx.Morph对象参数
		set: function (options) {
			// 获取元素上的Fx.Morph实例后先执行cancel方法，取消特效的执行，然后再设置可选参数
			this.get('morph').cancel().setOptions(options);
			return this;
		},

		// getter获取Fx.Morph对象
		get: function () {
			// 先从临时对象读取，看有没缓存到Fx.Morph实例
			var morph = this.retrieve('morph');
			if (!morph) {
				// 如果没有缓存，则保存一个新的Fx.Morph实例
				morph = new Fx.Morph(this, { link: 'cancel' });
				this.store('morph', morph);
			}
			return morph;
		}

	};

	// #endregion

	// #region - Element Method -

	// 元素调用Fx.Morph的快捷方式
	Element.implement({

		/**
		* @element method: morph
		* @param properties - (mixed) 可以是包含CSS属性键值对的对象, 或是一个CSS选择器(必须在页面中已定义).如果对CSS属性只给出一个值,则变换的时候将把元素当前的属性值作为起始值.
		* @returns: (element) 返回主调元素
		* @description: 对元素执行指定属性值的动画特效变换
		**/
		morph: function (props) {
			// 这是使用上面的getter取Fx.Morph实例，再start，props可以为多个样式属性数组或CSS选择符
			this.get('morph').start(props);
			return this;
		}

	});

	// #endregion

	// #endregion

	// #region -- Fx.Transitions --

	/*
	---

	name: Fx.Transitions

	description: Contains a set of advanced transitions to be used with any of the Fx Classes.

	license: MIT-style license.

	credits:
	- Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.

	requires: Fx

	provides: Fx.Transitions

	...
	*/

	Fx.implement({

		getTransition: function () {
			var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
			if (typeof trans == 'string') {
				var data = trans.split(':');
				trans = Fx.Transitions;
				trans = trans[data[0]] || trans[data[0].capitalize()];
				if (data[1]) {
					trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
				}
			}
			return trans;
		}

	});

	Fx.Transition = function (transition, params) {
		params = Array.from(params);
		var easeIn = function (pos) {
			return transition(pos, params);
		};
		return Object.append(easeIn, {
			easeIn: easeIn,
			easeOut: function (pos) {
				return 1 - transition(1 - pos, params);
			},
			easeInOut: function (pos) {
				return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2;
			}
		});
	};

	Fx.Transitions = {

		linear: function (zero) {
			return zero;
		}

	};

	Fx.Transitions.extend = function (transitions) {
		for (var transition in transitions) {
			Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
		}
	};

	Fx.Transitions.extend({

		Pow: function (p, x) {
			return Math.pow(p, x && x[0] || 6);
		},

		Expo: function (p) {
			return Math.pow(2, 8 * (p - 1));
		},

		Circ: function (p) {
			return 1 - Math.sin(Math.acos(p));
		},

		Sine: function (p) {
			return 1 - Math.cos(p * Math.PI / 2);
		},

		Back: function (p, x) {
			x = x && x[0] || 1.618;
			return Math.pow(p, 2) * ((x + 1) * p - x);
		},

		Bounce: function (p) {
			var value;
			for (var a = 0, b = 1; 1; a += b, b /= 2) {
				if (p >= (7 - 4 * a) / 11) {
					value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
					break;
				}
			}
			return value;
		},

		Elastic: function (p, x) {
			return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3);
		}

	});

	['Quad', 'Cubic', 'Quart', 'Quint'].each(function (transition, i) {
		Fx.Transitions[transition] = new Fx.Transition(function (p) {
			return Math.pow(p, i + 2);
		});
	});

	// #endregion

	// #region -- Chain.Wait --

	/**
	* name: Chain.Wait
	* 
	* description: value, Adds a method to inject pauses between chained events.
	* 
	* authors:
	*		- Aaron Newton
	* 
	* requires:
	*		- Core/Chain
	*		- Core/Element
	*		- Core/Fx
	*		- /MooTools.More
	* 
	* provides: [Chain.Wait]
	**/

	(function () {

		var wait = {
			wait: function (duration) {
				return this.chain(function () {
					this.callChain.delay(duration == null ? 500 : duration, this);
					return this;
				} .bind(this));
			}
		};

		Chain.implement(wait);

		if (this.Fx) { Fx.implement(wait); }

		if (this.Element && Element.implement && this.Fx) {
			Element.implement({

				chains: function (effects) {
					Array.from(effects || ['tween', 'morph', 'reveal']).each(function (effect) {
						effect = this.get(effect);
						if (!effect) { return; }
						effect.setOptions({
							link: 'chain'
						});
					}, this);
					return this;
				},

				pauseFx: function (duration, effect) {
					this.chains(effect).get(effect || 'tween').wait(duration);
					return this;
				}

			});
		}

	})();

	// #endregion

	// #region -- Fx.Elements --

	/**
	* name: Fx.Elements
	* 
	* description: Effect to change any number of CSS properties of any number of Elements.
	* 
	* authors:
	*		- Valerio Proietti
	* 
	* requires:
	*		- Core/Fx.CSS
	*		- /MooTools.More
	* 
	* provides: [Fx.Elements]
	*
	*	源码分析: 苦苦的苦瓜(http://hmking.blog.51cto.com)
	**/

	/**
	* @Fx.Elements - 批量对元素进行特效应用
	**/
	Fx.Elements = new Class({

		// 继承自Fx.CSS类
		Extends: Fx.CSS,

		/**
		* @method: initialize
		* @param elements - (array) 元素集合
		* @param options - (object, optional) Fx的可选项
		* @returns: (object) Fx.Elements实例
		* @description: 构造函数
		**/
		initialize: function (elements, options) {
			// 当前的作用对象集合
			this.elements = this.subject = $$(elements);
			// 调用父类的构造函数
			this.parent(options);
		},

		// 根据初始值，结束值和变量批量对元素求目标值
		compute: function (from, to, delta) {
			var now = {};

			// 遍历集合中每项对应的目标值
			for (var i in from) {
				// 取出各项初始值及结束值
				var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
				// 计算各项的属性目标值
				for (var p in iFrom) {
					iNow[p] = this.parent(iFrom[p], iTo[p], delta);
				}
			}

			return now;
		},

		/**
		* @method: set
		* @param now - (object) 一个包含了元素所对应特效值的键值对对象, 元素集合中的每个元素用一个数字字符来引用(比如: "0"代表集合中第一个元素,"1"代表集合中第二个元素, 依次类推)
		*		{
		*			'0': {
		*				'height': 200,
		*				'opacity': 0
		*			},
		*			'1': {
		*				'width': 300,
		*				'opacity': 1
		*			}
		*		}
		* @returns: (object) - 主调Fx.Elements实例
		* @description: 将每个应用此批量特效的元素的特效值立即设为指定值
		**/
		set: function (now) {
			// 遍历元素集合
			for (var i in now) {
				if (!this.elements[i]) { continue; }

				var iNow = now[i];
				// 分别设置每一项样式值
				for (var p in iNow) {
					this.render(this.elements[i], p, iNow[p], this.options.unit);
				}
			}

			return this;
		},

		/**
		* @method: start
		* @param obj - (object) 一个包含了元素所对应特效值的键值对对象, 元素集合中的每个元素用一个数字字符来引用(比如: "0"代表集合中第一个元素,"1"代表集合中第二个元素, 依次类推)
		*		{
		*			'0': { //第一个元素的opacity和width属性的变换
		*				'opacity': [0,1],
		*				'width': [100,200]
		*			},
		*			'4': { //第五个元素的opacity
		*				'opacity': [0.2, 0.5]
		*			}
		*		}
		* @returns: (object) - 主调Fx.Elements实例
		* @description: 批量执行元素的特效变换
		**/
		start: function (obj) {
			// 检查当前特效运行状态，决定是否运行新特效
			if (!this.check(obj)) { return this; }
			// 各元素对应的CSS属性初始及结束值集合
			var from = {}, to = {};

			// 遍历提供的元素集合对象
			for (var i in obj) {
				// 如果元素不存在，遍历下一个元素
				if (!this.elements[i]) { continue; }

				var iProps = obj[i],
						iFrom = from[i] = {},
						iTo = to[i] = {};

				// 对每项CSS属性值进行解释，得到每项CSS属性的from和to的值
				for (var p in iProps) {
					var parsed = this.prepare(this.elements[i], p, iProps[p]);
					iFrom[p] = parsed.from;
					iTo[p] = parsed.to;
				}
			}

			// 调用父同名方法
			return this.parent(from, to);
		}

	});

	// #endregion

	// #region -- Fx.Accordion --

	/**
	* name: Fx.Accordion
	* 
	* description: An Fx.Elements extension which allows you to easily create accordion type controls.
	* 
	* authors:
	*		- Valerio Proietti
	* 
	* requires:
	*		- Core/Element.Event
	*		- /Fx.Elements
	* 
	* provides: [Fx.Accordion]
	**/

	// 类似手风琴的伸缩效果
	Fx.Accordion = new Class({

		// 继承自Fx.Elements
		Extends: Fx.Elements,

		options: {/*
		// 伸展显示事件
		onActive: function(toggler, section){},
		// 压缩隐藏事件
		onBackground: function(toggler, section){},*/
			// 是否固定高度
			fixedHeight: false,
			// 是否固定宽度
			fixedWidth: false,
			// 显示的索引值
			display: 0,
			show: false,
			// 高度的显示方式
			height: true,
			// 宽度的显示方式
			width: false,
			// 透明度的显示方式
			opacity: true,
			// 永远隐藏
			alwaysHide: false,
			trigger: 'click',
			initialDisplayFx: true,
			resetHeight: true
		},

		// 构造函数
		initialize: function () {
			var defined = function (obj) {
				return obj != null;
			};

			// 利用Array.link读取参数并归类
			var params = Array.link(arguments, {
				'container': Type.isElement, //deprecated
				'options': Type.isObject,
				'togglers': defined,
				'elements': defined
			});
			// 调用父类Fx.Elements的同名方法
			this.parent(params.elements, params.options);

			var options = this.options,
			// 取能toggle的Elements(点击时伸缩的对象)
					togglers = this.togglers = $$(params.togglers);

			// 最后激活的对象索引
			this.previous = -1;
			this.internalChain = new Chain();

			if (options.alwaysHide) {
				this.options.link = 'chain';
			}

			// 如果设置显示的索引值
			if (options.show || this.options.show === 0) {
				// 忽略display的设置
				options.display = false;
				// 更新指向
				this.previous = options.show;
			}

			// 如果手工执行start方法
			if (options.start) {
				// 忽略display指定的值
				options.display = false;
				// 忽略show指定的值
				options.show = false;
			}

			// 提供Fx.Eleemnts中变化的属性集
			var effects = this.effects = {};

			// 如果指定透明度, 设效果中的透明属性值为完全透明
			if (options.opacity) {
				effects.opacity = 'fullOpacity';
			}
			// 如果指定宽度, 当同时指定固定宽度时, 设效果中的宽度属性值为全尺寸宽度, 否则为当前宽度
			if (options.width) {
				effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
			}
			// 如果指定高度, 当同时指定固定高度时, 设效果中的高度属性值为全尺寸宽度, 否则为当前高度
			if (options.height) {
				effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
			}

			// 遍历处理, 为每一项添加事件
			for (var i = 0, l = togglers.length; i < l; i++) {
				this.addSection(togglers[i], this.elements[i]);
			}

			// 遍历找到显示的指定项
			this.elements.each(function (el, i) {
				// 如果指定显示当前项
				if (options.show === i) {
					// 触发onActive事件
					this.fireEvent('active', [togglers[i], el]);
				} else {
					// 设置非显示项的相关CSS属性为0
					for (var fx in effects) {
						el.setStyle(fx, 0);
					}
				}
			}, this);

			// 
			if (options.display || options.display === 0 || options.initialDisplayFx === false) {
				this.display(options.display, options.initialDisplayFx);
			}

			if (options.fixedHeight !== false) {
				options.resetHeight = false;
			}
			this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
		},

		// 添加节点
		// togger为伸缩句柄,eleemnt为内容容器,pos为添加到的位置索引
		addSection: function (toggler, element) {
			// 取对象，然后将对象包含进各自的对象集合
			toggler = document.id(toggler);
			element = document.id(element);
			this.togglers.include(toggler);
			this.elements.include(element);

			var togglers = this.togglers,
					options = this.options,
					test = togglers.contains(toggler), // 测试togglers集合中是否已包含toggler
					idx = togglers.indexOf(toggler), // 取当前索引值
					displayer = this.display.pass(idx, this);

			toggler.store('accordion:display', displayer).addEvent(options.trigger, displayer);

			// 如果指定高度, 为了兼容, 将会影响高度值的padding和boder的上下值置0
			if (options.height) {
				element.setStyles({ 'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none' });
			}
			// 如果指定宽度, 为了兼容, 将会影响宽度值的padding和boder的左右值置0
			if (options.width) {
				element.setStyles({ 'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none' });
			}

			element.fullOpacity = 1;
			// 如果设置固定宽度
			if (options.fixedWidth) {
				element.fullWidth = options.fixedWidth;
			}
			// 如果设置固定高度
			if (options.fixedHeight) {
				element.fullHeight = options.fixedHeight;
			}
			// 剪切溢出内容
			element.setStyle('overflow', 'hidden');

			if (!test) {
				// 设置容器相关CSS属性为0
				for (var fx in this.effects) {
					element.setStyle(fx, 0);
				}
			}
			return this;
		},

		removeSection: function (toggler, displayIndex) {
			var togglers = this.togglers,
					idx = togglers.indexOf(toggler),
					element = this.elements[idx];

			var remover = function () {
				togglers.erase(toggler);
				this.elements.erase(element);
				this.detach(toggler);
			} .bind(this);

			if (this.now == idx || displayIndex != null) {
				this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
			} else {
				remover();
			}
			return this;
		},

		detach: function (toggler) {
			var remove = function (toggler) {
				toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
			} .bind(this);

			if (!toggler) {
				this.togglers.each(remove);
			} else {
				remove(toggler);
			}
			return this;
		},

		display: function (index, useFx) {
			if (!this.check(index, useFx)) { return this; }

			var obj = {},
					elements = this.elements,
					options = this.options,
					effects = this.effects;

			if (useFx == null) {
				useFx = true;
			}
			if (typeOf(index) == 'element') {
				index = elements.indexOf(index);
			}
			if (index == this.previous && !options.alwaysHide) {
				return this;
			}

			if (options.resetHeight) {
				var prev = elements[this.previous];
				if (prev && !this.selfHidden) {
					for (var fx in effects) {
						prev.setStyle(fx, prev[effects[fx]]);
					}
				}
			}

			if ((this.timer && options.link == 'chain') || (index === this.previous && !options.alwaysHide)) {
				return this;
			}

			this.previous = index;
			this.selfHidden = false;

			elements.each(function (el, i) {
				obj[i] = {};
				var hide;
				if (i != index) {
					hide = true;
				} else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)) {
					hide = true;
					this.selfHidden = true;
				}
				this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
				for (var fx in effects) {
					obj[i][fx] = hide ? 0 : el[effects[fx]];
				}
				if (!useFx && !hide && options.resetHeight) {
					obj[i].height = 'auto';
				}
			}, this);

			this.internalChain.clearChain();
			this.internalChain.chain(function () {
				if (options.resetHeight && !this.selfHidden) {
					var el = elements[index];
					if (el) {
						el.setStyle('height', 'auto');
					}
				}
			} .bind(this));

			return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
		}

	});

	// #endregion

	// #region -- Fx.Move --

	/**
	* name: Fx.Move
	* 
	* description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
	* 
	* authors:
	*		- Aaron Newton
	* 
	* requires:
	*		- Core/Fx.Morph
	*		- /Element.Position
	* 
	* provides: [Fx.Move]
	**/

	Fx.Move = new Class({

		Extends: Fx.Morph,

		options: {
			relativeTo: document.body,
			position: 'center',
			edge: false,
			offset: { x: 0, y: 0 }
		},

		start: function (destination) {
			var element = this.element,
			topLeft = element.getStyles('top', 'left');
			if (topLeft.top == 'auto' || topLeft.left == 'auto') {
				element.setPosition(element.getPosition(element.getOffsetParent()));
			}
			return this.parent(element.position(Object.merge({}, this.options, destination, { returnPos: true })));
		}

	});

	Element.Properties.move = {

		set: function (options) {
			this.get('move').cancel().setOptions(options);
			return this;
		},

		get: function () {
			var move = this.retrieve('move');
			if (!move) {
				move = new Fx.Move(this, { link: 'cancel' });
				this.store('move', move);
			}
			return move;
		}

	};

	Element.implement({

		move: function (options) {
			this.get('move').start(options);
			return this;
		}

	});

	// #endregion

	// #region -- Fx.Reveal --

	/**
	* name: Fx.Reveal
	* 
	* description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
	* 
	* authors:
	*		- Aaron Newton
	* 
	* requires:
	*		- Core/Fx.Morph
	*		- /Element.Shortcuts
	*		- /Element.Measure
	* 
	* provides: [Fx.Reveal]
	**/

	(function () {

		var hideTheseOf = function (object) {
			var hideThese = object.options.hideInputs;
			if (window.OverText) {
				var otClasses = [null];
				OverText.each(function (ot) {
					otClasses.include('.' + ot.options.labelClass);
				});
				if (otClasses) {
					hideThese += otClasses.join(', ');
				}
			}
			return (hideThese) ? object.element.getElements(hideThese) : null;
		};

		Fx.Reveal = new Class({

			Extends: Fx.Morph,

			options: {/*
				onShow: function(thisElement){},
				onHide: function(thisElement){},
				onComplete: function(thisElement){},
				heightOverride: null,
				widthOverride: null,*/
				link: 'cancel',
				styles: ['padding', 'border', 'margin'],
				transitionOpacity: !Browser.ie6,
				mode: 'vertical',
				display: function () {
					return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
				},
				opacity: 1,
				hideInputs: Browser.ie ? 'select, input, textarea, object, embed' : null
			},

			dissolve: function () {
				if (!this.hiding && !this.showing) {
					if (this.element.getStyle('display') != 'none') {
						this.hiding = true;
						this.showing = false;
						this.hidden = true;
						this.cssText = this.element.style.cssText;

						var startStyles = this.element.getComputedSize({
							styles: this.options.styles,
							mode: this.options.mode
						});
						if (this.options.transitionOpacity) {
							startStyles.opacity = this.options.opacity;
						}

						var zero = {};
						Object.each(startStyles, function (style, name) {
							zero[name] = [style, 0];
						});

						this.element.setStyles({
							display: Function.from(this.options.display).call(this),
							overflow: 'hidden'
						});

						var hideThese = hideTheseOf(this);
						if (hideThese) hideThese.setStyle('visibility', 'hidden');

						this.$chain.unshift(function () {
							if (this.hidden) {
								this.hiding = false;
								this.element.style.cssText = this.cssText;
								this.element.setStyle('display', 'none');
								if (hideThese) {
									hideThese.setStyle('visibility', 'visible');
								}
							}
							this.fireEvent('hide', this.element);
							this.callChain();
						} .bind(this));

						this.start(zero);
					} else {
						this.callChain.delay(10, this);
						this.fireEvent('complete', this.element);
						this.fireEvent('hide', this.element);
					}
				} else if (this.options.link == 'chain') {
					this.chain(this.dissolve.bind(this));
				} else if (this.options.link == 'cancel' && !this.hiding) {
					this.cancel();
					this.dissolve();
				}
				return this;
			},

			reveal: function () {
				if (!this.showing && !this.hiding) {
					if (this.element.getStyle('display') == 'none') {
						this.hiding = false;
						this.showing = true;
						this.hidden = false;
						this.cssText = this.element.style.cssText;

						var startStyles;
						this.element.measure(function () {
							startStyles = this.element.getComputedSize({
								styles: this.options.styles,
								mode: this.options.mode
							});
						} .bind(this));
						if (this.options.heightOverride != null) {
							startStyles.height = this.options.heightOverride.toInt();
						}
						if (this.options.widthOverride != null) {
							startStyles.width = this.options.widthOverride.toInt();
						}
						if (this.options.transitionOpacity) {
							this.element.setStyle('opacity', 0);
							startStyles.opacity = this.options.opacity;
						}

						var zero = {
							height: 0,
							display: Function.from(this.options.display).call(this)
						};
						Object.each(startStyles, function (style, name) {
							zero[name] = 0;
						});
						zero.overflow = 'hidden';

						this.element.setStyles(zero);

						var hideThese = hideTheseOf(this);
						if (hideThese) {
							hideThese.setStyle('visibility', 'hidden');
						}

						this.$chain.unshift(function () {
							this.element.style.cssText = this.cssText;
							this.element.setStyle('display', Function.from(this.options.display).call(this));
							if (!this.hidden) {
								this.showing = false;
							}
							if (hideThese) {
								hideThese.setStyle('visibility', 'visible');
							}
							this.callChain();
							this.fireEvent('show', this.element);
						} .bind(this));

						this.start(startStyles);
					} else {
						this.callChain();
						this.fireEvent('complete', this.element);
						this.fireEvent('show', this.element);
					}
				} else if (this.options.link == 'chain') {
					this.chain(this.reveal.bind(this));
				} else if (this.options.link == 'cancel' && !this.showing) {
					this.cancel();
					this.reveal();
				}
				return this;
			},

			toggle: function () {
				if (this.element.getStyle('display') == 'none') {
					this.reveal();
				} else {
					this.dissolve();
				}
				return this;
			},

			cancel: function () {
				this.parent.apply(this, arguments);
				if (this.cssText != null) {
					this.element.style.cssText = this.cssText;
				}
				this.hiding = false;
				this.showing = false;
				return this;
			}

		});

		Element.Properties.reveal = {

			set: function (options) {
				this.get('reveal').cancel().setOptions(options);
				return this;
			},

			get: function () {
				var reveal = this.retrieve('reveal');
				if (!reveal) {
					reveal = new Fx.Reveal(this);
					this.store('reveal', reveal);
				}
				return reveal;
			}

		};

		Element.Properties.dissolve = Element.Properties.reveal;

		Element.implement({

			reveal: function (options) {
				this.get('reveal').setOptions(options).reveal();
				return this;
			},

			dissolve: function (options) {
				this.get('reveal').setOptions(options).dissolve();
				return this;
			},

			nix: function (options) {
				var params = Array.link(arguments, { destroy: Type.isBoolean, options: Type.isObject });
				this.get('reveal').setOptions(options).dissolve().chain(function () {
					this[params.destroy ? 'destroy' : 'dispose']();
				} .bind(this));
				return this;
			},

			wink: function () {
				var params = Array.link(arguments, { duration: Type.isNumber, options: Type.isObject });
				var reveal = this.get('reveal').setOptions(params.options);
				reveal.reveal().chain(function () {
					(function () {
						reveal.dissolve();
					}).delay(params.duration || 2000);
				});
			}

		});

	})();

	// #endregion

	// #region -- Fx.Scroll --

	/**
	* name: Fx.Scroll
	* 
	* description: Effect to smoothly scroll any element, including the window.
	* 
	* authors:
	*		- Valerio Proietti
	* 
	* requires:
	*		- Core/Fx
	*		- Core/Element.Event
	*		- Core/Element.Dimensions
	*		- /MooTools.More
	* 
	* provides: [Fx.Scroll]
	**/

	// 平滑的滚动效果
	(function () {

		Fx.Scroll = new Class({

			// 继承自Fx类
			Extends: Fx,

			options: {
				// 滚动时的偏移坐标量
				offset: { x: 0, y: 0 },
				// 滚轮时是否自动停止滚动效果
				wheelStops: true
			},

			initialize: function (element, options) {
				// 特效作用的对象
				this.element = this.subject = document.id(element);
				// 调用父类同名方法
				this.parent(options);

				// 如果提供作用对象有误, 换成当前文档的body节点对象
				if (typeOf(this.element) != 'element') {
					this.element = document.id(this.element.getDocument().body);
				}

				// 如果设置滚轮停止
				if (this.options.wheelStops) {
					// 为滚轮事件提供取消滚动的对象绑定, 写在下面wheelStops的判断里应该会更好
					var stopper = this.element,
							cancel = this.cancel.pass(false, this);
					// 在滚动开始前添加滚轮事件监听
					this.addEvent('start', function () {
						stopper.addEvent('mousewheel', cancel);
					}, true);
					// 在滚动结束后移除滚轮事件监听
					this.addEvent('complete', function () {
						stopper.removeEvent('mousewheel', cancel);
					}, true);
				}
			},

			// 设置目标值
			set: function () {
				// 给参数对象降维
				var now = Array.flatten(arguments);
				if (Browser.firefox) {
					now = [Math.round(now[0]), Math.round(now[1])]; // not needed anymore in newer firefox versions
				}
				// 滚动到指定坐标
				this.element.scrollTo(now[0], now[1]);
				return this;
			},

			// 根据初始值，结束值和变量因子求目标值
			compute: function (from, to, delta) {
				return [0, 1].map(function (i) {
					return Fx.compute(from[i], to[i], delta);
				});
			},

			// 开始滚动
			start: function (x, y) {
				// 检查约束设置
				if (!this.check(x, y)) { return this; }
				// 获取当前滚动位置
				var scroll = this.element.getScroll();
				// 调用父类Fx的同名方法
				return this.parent([scroll.x, scroll.y], [x, y]);
			},

			calculateScroll: function (x, y) {
				var element = this.element,
						scrollSize = element.getScrollSize(), // 获取对象滚动尺寸
						scroll = element.getScroll(), // 获取对象当前滚动位置
						size = element.getSize(), // 获取对象尺寸
						offset = this.options.offset,
						values = { x: x, y: y };

				for (var z in values) {
					if (!values[z] && values[z] !== 0) {
						values[z] = scroll[z];
					}
					if (typeOf(values[z]) != 'number') {
						values[z] = scrollSize[z] - size[z];
					}
					values[z] += offset[z];
				}

				return [values.x, values.y];
			},

			// 滚动到顶部, x轴滚动位置不变
			toTop: function () {
				return this.start.apply(this, this.calculateScroll(false, 0));
			},

			// 滚动到左边, y轴滚动位置不变
			toLeft: function () {
				return this.start.apply(this, this.calculateScroll(0, false));
			},

			// 滚动到右边, y轴滚动位置不变
			toRight: function () {
				return this.start.apply(this, this.calculateScroll('right', false));
			},

			// 滚动到底部, x轴滚动位置不变
			toBottom: function () {
				return this.start.apply(this, this.calculateScroll(false, 'bottom'));
			},

			// 滚动到指定Element的绝对坐标位置
			toElement: function (el, axes) {
				axes = axes ? Array.from(axes) : ['x', 'y'];
				var scroll = isBody(this.element) ? { x: 0, y: 0} : this.element.getScroll();
				// 获取el的绝对坐标值
				var position = Object.map(document.id(el).getPosition(this.element), function (value, axis) {
					return axes.contains(axis) ? value + scroll[axis] : false;
				});
				// 开始滚动
				return this.start.apply(this, this.calculateScroll(position.x, position.y));
			},

			toElementEdge: function (el, axes, offset) {
				axes = axes ? Array.from(axes) : ['x', 'y'];
				el = document.id(el);
				var to = {},
						position = el.getPosition(this.element),
						size = el.getSize(),
						scroll = this.element.getScroll(),
						containerSize = this.element.getSize(),
						edge = {
							x: position.x + size.x,
							y: position.y + size.y
						};

				['x', 'y'].each(function (axis) {
					if (axes.contains(axis)) {
						if (edge[axis] > scroll[axis] + containerSize[axis]) {
							to[axis] = edge[axis] - containerSize[axis];
						}
						if (position[axis] < scroll[axis]) {
							to[axis] = position[axis];
						}
					}
					if (to[axis] == null) {
						to[axis] = scroll[axis];
					}
					if (offset && offset[axis]) {
						to[axis] = to[axis] + offset[axis];
					}
				}, this);

				if (to.x != scroll.x || to.y != scroll.y) {
					this.start(to.x, to.y);
				}
				return this;
			},

			toElementCenter: function (el, axes, offset) {
				axes = axes ? Array.from(axes) : ['x', 'y'];
				el = document.id(el);
				var to = {},
						position = el.getPosition(this.element),
						size = el.getSize(),
						scroll = this.element.getScroll(),
						containerSize = this.element.getSize();

				['x', 'y'].each(function (axis) {
					if (axes.contains(axis)) {
						to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
					}
					if (to[axis] == null) {
						to[axis] = scroll[axis];
					}
					if (offset && offset[axis]) {
						to[axis] = to[axis] + offset[axis];
					}
				}, this);

				if (to.x != scroll.x || to.y != scroll.y) {
					this.start(to.x, to.y);
				}
				return this;
			}

		});

		function isBody(element) {
			return (/^(?:body|html)$/i).test(element.tagName);
		}

	})();

	// #endregion

	// #region -- Fx.Slide --

	/**
	* name: Fx.Slide
	* 
	* description: Effect to slide an element in and out of view.
	* 
	* authors:
	*		- Valerio Proietti
	* 
	* requires:
	*		- Core/Fx
	*		- Core/Element.Style
	*		- /MooTools.More
	* 
	* provides: [Fx.Slide]
	**/

	// 滑入滑出效果, 需要创建一个包装容器, 再配合margin来实现
	// 参考我佛山人关于mootools1.2-Fx.Slide的源码分析(http://space.flash8.net/space/?18713/viewspace-407778.html)
	Fx.Slide = new Class({

		// /继承自Fx
		Extends: Fx,

		options: {
			mode: 'vertical',
			wrapper: false,
			hideOverflow: true,
			resetHeight: false
		},

		// 覆盖父类Fx的同名构造函数
		initialize: function (element, options) {
			// 特效的作用对象
			element = this.element = this.subject = document.id(element);
			// 调用父类Fx的同名方法
			this.parent(options);
			options = this.options;

			// 先从缓存对象中取当前作用对象的包装容器
			var wrapper = element.retrieve('wrapper'),
					styles = element.getStyles('margin', 'position', 'overflow');

			if (options.hideOverflow) {
				styles = Object.append(styles, { overflow: 'hidden' });
			}
			if (options.wrapper) {
				wrapper = document.id(options.wrapper).setStyles(styles);
			}

			if (!wrapper) wrapper = new Element('div', {
				styles: styles
			}).wraps(element);

			element.store('wrapper', wrapper).setStyle('margin', 0);
			if (element.getStyle('overflow') == 'visible') {
				element.setStyle('overflow', 'hidden');
			}

			this.now = [];
			// 滑动状态标记
			this.open = true;
			this.wrapper = wrapper;

			// 添加onComplete事件, 以修正open属性标记的值
			this.addEvent('complete', function () {
				this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
				if (this.open && this.options.resetHeight) {
					wrapper.setStyle('height', '');
				}
			}, true);
		},

		// 垂直方向滑动时的关键属性设置
		vertical: function () {
			// 垂直滑动时主要使用margin-top属性,控制作用对象在包装窗口的垂直位置
			this.margin = 'margin-top';
			// 包装容器的高度属性
			this.layout = 'height';
			// 包装窗口的高度值
			this.offset = this.element.offsetHeight;
		},

		// 水平方向滑动时的关键属性设置
		horizontal: function () {
			// 水平滑动时主要使用margin-left属性,控制作用对象在包装窗口的水平位置
			this.margin = 'margin-left';
			// 包装容器的宽度属性
			this.layout = 'width';
			// 包装窗口的宽度值
			this.offset = this.element.offsetWidth;
		},

		// 设置目标值
		set: function (now) {
			// 设置作用对象在包装容器内的水平或垂直位置
			this.element.setStyle(this.margin, now[0]);
			// 设置包装窗口的宽度或高度值
			this.wrapper.setStyle(this.layout, now[1]);
			return this;
		},

		// 根据初始值，结束值和变量因子求目标值
		compute: function (from, to, delta) {
			return [0, 1].map(function (i) {
				return Fx.compute(from[i], to[i], delta);
			});
		},

		// 开始滑动
		start: function (how, mode) {
			// 检查约束
			if (!this.check(how, mode)) { return this; }
			// 根据配置参数选择滑动模式的相关属性
			this[mode || this.options.mode]();

			var margin = this.element.getStyle(this.margin).toInt(), // 取当前作用对象的外补白值
					layout = this.wrapper.getStyle(this.layout).toInt(), // 取包装容器的尺寸
					caseIn = [[margin, layout], [0, this.offset]], // 测入时的目标值集合
					caseOut = [[margin, layout], [-this.offset, 0]], // 测出时的目标值集合
					start;

			// 滑动方式选择
			switch (how) {
				case 'in':
					start = caseIn;
					break;

				case 'out':
					start = caseOut;
					break;

				case 'toggle':
					start = (layout == 0) ? caseIn : caseOut;
			}
			return this.parent(start[0], start[1]);
		},

		// 滑入的快捷方法
		slideIn: function (mode) {
			return this.start('in', mode);
		},

		// 滑出的快捷方法
		slideOut: function (mode) {
			return this.start('out', mode);
		},

		// 隐藏滑动对象
		hide: function (mode) {
			this[mode || this.options.mode]();
			// 显示隐藏的标记
			this.open = false;
			return this.set([-this.offset, 0]);
		},

		// 显示滑动对象
		show: function (mode) {
			this[mode || this.options.mode]();
			// 显示隐藏的标记
			this.open = true;
			return this.set([0, this.offset]);
		},

		// 开关
		toggle: function (mode) {
			return this.start('toggle', mode);
		}

	});

	// 为Element添加slide属性
	Element.Properties.slide = {

		// setter
		set: function (options) {
			// 从缓存对象读取作用于当前Element的Fx.Slide实例
			this.get('slide').cancel().setOptions(options);
			return this;
		},

		// getter
		get: function () {
			// 从缓存对象读取作用于当前Element的Fx.Slide实例
			var slide = this.retrieve('slide');
			// 如果不存在缓存
			if (!slide) {
				// 新建并缓存一个Fx.Slide实例对象
				slide = new Fx.Slide(this, { link: 'cancel' });
				this.store('slide', slide);
			}
			return slide;
		}

	};

	// 扩展的slide方法
	Element.implement({

		// 为Element添加的Fx.Slide快捷方法
		slide: function (how, mode) {
			// 指定滑动方式
			how = how || 'toggle';
			// 取缓存中的Fx.Slide实例
			var slide = this.get('slide'), toggle;
			// 不同滑动方式的处理
			switch (how) {
				case 'hide':
					slide.hide(mode);
					break;

				case 'show':
					slide.show(mode);
					break;

				case 'toggle':
					// 先从缓存对象取状态标记
					var flag = this.retrieve('slide:flag', slide.open);
					// 根据标记执行滑入还是滑出
					slide[flag ? 'slideOut' : 'slideIn'](mode);
					// 缓存状态标记
					this.store('slide:flag', !flag);
					// 表明本次执行定了how的参数值为toggle, 如果不false, 下面将会删除缓存的标记
					toggle = true;
					break;

				default:
					// 默认滑动方式
					slide.start(how, mode);
			}
			// 如果没有使用toggle滑动方式, 删除缓存的标记
			if (!toggle) { this.eliminate('slide:flag'); }
			return this;
		}

	});

	// #endregion

	// #region -- Fx.SmoothScroll --

	/**
	* name: Fx.SmoothScroll
	* 
	* description: Class for creating a smooth scrolling effect to all internal links on the page.
	* 
	* authors:
	*		- Valerio Proietti
	* 
	* requires:
	*		- Core/Slick.Finder
	*		- /Fx.Scroll
	* 
	* provides: [Fx.SmoothScroll]
	**/

	// 更平滑的滚动效果
	Fx.SmoothScroll = new Class({

		// 继承自Fx.Scroll
		Extends: Fx.Scroll,

		options: {
			axes: ['x', 'y']
		},

		// 构造函数, 将覆盖父类Fx.Scroll的构造函数
		initialize: function (options, context) {
			// 限定所作用的对象
			context = context || document;
			this.doc = context.getDocument();
			// 调用父类Fx.Scroll的构造函数
			this.parent(this.doc, options);

			var win = context.getWindow(),
					location = win.location.href.match(/^[^#]*/)[0] + '#', // 获取不包括锚点的当前页地址
					links = $$(this.options.links || this.doc.links); // 获取所有链接

			// 遍历所有链接, 只处理锚点在当前页的锚点链接
			links.each(function (link) {
				// 链接不是当前页的锚点
				if (link.href.indexOf(location) != 0) {
					return;
				}
				// 取锚点(这里直接用link.hash应该更简单)
				var anchor = link.href.substr(location.length);
				// 取到锚点名, 然后为当前链接添加事件监听, 在单击时将自动滚动到id值为锚点名的对象
				if (anchor) {
					this.useLink(link, anchor);
				}
			}, this);

			this.addEvent('complete', function () {
				win.location.hash = this.anchor;
				this.element.scrollTo(this.to[0], this.to[1]);
			}, true);
		},

		// 使用当前链接, 对当前链接的事件监听处理
		useLink: function (link, anchor) {

			// 监听链接的单击事件
			link.addEvent('click', function (event) {
				var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
				if (!el) { return; }

				// 阻止事件冒泡及返回值
				event.preventDefault();
				// 滑动到id为anchor的对象
				this.toElement(el, this.options.axes).chain(function () {
					this.fireEvent('scrolledTo', [link, el]);
				} .bind(this));

				// 转为属性, 使得在其它方法或事件内可以使用
				this.anchor = anchor;

			} .bind(this));

			return this;
		}
	});

	// #endregion

	// #region -- Fx.Sort --

	/**
	* name: Fx.Sort
	* 
	* description: Defines Fx.Sort, a class that reorders lists with a transition.
	* 
	* authors:
	*		- Aaron Newton
	* 
	* requires:
	*		- Core/Element.Dimensions
	*		- /Fx.Elements
	*		- /Element.Measure
	* 
	* provides: [Fx.Sort]
	**/

	Fx.Sort = new Class({

		Extends: Fx.Elements,

		options: {
			mode: 'vertical'
		},

		initialize: function (elements, options) {
			this.parent(elements, options);
			this.elements.each(function (el) {
				if (el.getStyle('position') == 'static') {
					el.setStyle('position', 'relative');
				}
			});
			this.setDefaultOrder();
		},

		setDefaultOrder: function () {
			this.currentOrder = this.elements.map(function (el, index) {
				return index;
			});
		},

		sort: function () {
			if (!this.check(arguments)) { return this; }
			var newOrder = Array.flatten(arguments);

			var top = 0,
					left = 0,
					next = {},
					zero = {},
					vert = this.options.mode == 'vertical';

			var current = this.elements.map(function (el, index) {
				var size = el.getComputedSize({ styles: ['border', 'padding', 'margin'] });
				var val;
				if (vert) {
					val = {
						top: top,
						margin: size['margin-top'],
						height: size.totalHeight
					};
					top += val.height - size['margin-top'];
				} else {
					val = {
						left: left,
						margin: size['margin-left'],
						width: size.totalWidth
					};
					left += val.width;
				}
				var plane = vert ? 'top' : 'left';
				zero[index] = {};
				var start = el.getStyle(plane).toInt();
				zero[index][plane] = start || 0;
				return val;
			}, this);

			this.set(zero);
			newOrder = newOrder.map(function (i) { return i.toInt(); });
			if (newOrder.length != this.elements.length) {
				this.currentOrder.each(function (index) {
					if (!newOrder.contains(index)) {
						newOrder.push(index);
					}
				});
				if (newOrder.length > this.elements.length) {
					newOrder.splice(this.elements.length - 1, newOrder.length - this.elements.length);
				}
			}
			var margin = 0;
			top = left = 0;
			newOrder.each(function (item) {
				var newPos = {};
				if (vert) {
					newPos.top = top - current[item].top - margin;
					top += current[item].height;
				} else {
					newPos.left = left - current[item].left;
					left += current[item].width;
				}
				margin = margin + current[item].margin;
				next[item] = newPos;
			}, this);
			var mapped = {};
			Array.clone(newOrder).sort().each(function (index) {
				mapped[index] = next[index];
			});
			this.start(mapped);
			this.currentOrder = newOrder;

			return this;
		},

		rearrangeDOM: function (newOrder) {
			newOrder = newOrder || this.currentOrder;
			var parent = this.elements[0].getParent();
			var rearranged = [];
			this.elements.setStyle('opacity', 0);
			//move each element and store the new default order
			newOrder.each(function (index) {
				rearranged.push(this.elements[index].inject(parent).setStyles({
					top: 0,
					left: 0
				}));
			}, this);
			this.elements.setStyle('opacity', 1);
			this.elements = $$(rearranged);
			this.setDefaultOrder();
			return this;
		},

		getDefaultOrder: function () {
			return this.elements.map(function (el, index) {
				return index;
			});
		},

		getCurrentOrder: function () {
			return this.currentOrder;
		},

		forward: function () {
			return this.sort(this.getDefaultOrder());
		},

		backward: function () {
			return this.sort(this.getDefaultOrder().reverse());
		},

		reverse: function () {
			return this.sort(this.currentOrder.reverse());
		},

		sortByElements: function (elements) {
			return this.sort(elements.map(function (el) {
				return this.elements.indexOf(el);
			}, this));
		},

		swap: function (one, two) {
			if (typeOf(one) == 'element') {
				one = this.elements.indexOf(one);
			}
			if (typeOf(two) == 'element') {
				two = this.elements.indexOf(two);
			}

			var newOrder = Array.clone(this.currentOrder);
			newOrder[this.currentOrder.indexOf(one)] = two;
			newOrder[this.currentOrder.indexOf(two)] = one;

			return this.sort(newOrder);
		}

	});

	// #endregion

	// #region -- Sortables --

	/**
	* name: Sortables
	* 
	* description: Class for creating a drag and drop sorting interface for lists of items.
	* 
	* authors:
	*		- Tom Occhino
	* 
	* requires:
	*		- Core/Fx.Morph
	*		- /Drag.Move
	* 
	* provides: [Sortables]
	**/

	// 拖放排序类
	var Sortables = this.Sortables = new Class({

		Implements: [Events, Options],

		options: {/*
		// 排序事件
		onSort: function(element, clone){},
		// 开始事件
		onStart: function(element, clone){},
		// 完成事件
		onComplete: function(element){},*/
			// 透明度
			opacity: 1,
			// 是否复制
			clone: false,
			// 是否使用形变动画
			revert: false,
			// 拖动句柄对象
			handle: false,
			// 约束
			dragOptions: {}
		},

		// 构造函数
		initialize: function (lists, options) {
			// 设置配置参数
			this.setOptions(options);

			this.elements = [];
			this.lists = [];
			this.idle = true;

			// 列表项可以单个, 也可以是符合
			this.addLists($$(document.id(lists) || lists));

			// 如果不克隆副本, 则不需要变形动画
			if (!this.options.clone) {
				this.options.revert = false;
			}
			// 如果指定变形动画效果
			if (this.options.revert) {
				this.effect = new Fx.Morph(null, Object.merge({
					duration: 250,
					link: 'cancel'
				}, this.options.revert));
			}
		},

		// 附加事件
		attach: function () {
			this.addLists(this.lists);
			return this;
		},

		// 移除事件
		detach: function () {
			this.lists = this.removeLists(this.lists);
			return this;
		},

		// 添加列表项
		addItems: function () {
			// 给参数降维再遍历
			Array.flatten(arguments).each(function (element) {
				// 加到数组
				this.elements.push(element);
				// 将关联本列表项的start方法缓存
				var start = element.retrieve('sortables:start', function (event) {
					this.start.call(this, event, element);
				} .bind(this));
				// 如果指定拖动句柄,从当前列表项中获取,否则将当前列表项作为句柄
				// 然后给句柄添加鼠标按下事件,触发start方法
				(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
			}, this);
			return this;
		},

		// 添加列表
		addLists: function () {
			// 给参数降维再遍历
			Array.flatten(arguments).each(function (list) {
				this.lists.include(list);
				// 添加当前列表下的项
				this.addItems(list.getChildren());
			}, this);
			return this;
		},

		// 移除列表项
		removeItems: function () {
			// 给参数降维再遍历
			return $$(Array.flatten(arguments).map(function (element) {
				// 从数组中删除
				this.elements.erase(element);
				// 取缓存中的事件绑定方法
				var start = element.retrieve('sortables:start');
				// 类似添加列表项时的判断, 找到句柄后移除事件
				(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);

				return element;
			}, this));
		},

		// 移除列表
		removeLists: function () {
			// 给参数降维再遍历
			return $$(Array.flatten(arguments).map(function (list) {
				// 从数组中删除
				this.lists.erase(list);
				// 移除当前列表的所有列表项,因为removeItems方法中自动给参数降维,所以可以直接传数组
				this.removeItems(list.getChildren());

				return list;
			}, this));
		},

		// 获取复制的对象
		getClone: function (event, element) {
			// 如果指定不复制, 返回新创建的div并插入到body
			if (!this.options.clone) {
				return new Element(element.tagName).inject(document.body);
			}
			// 如果参数为函数, 修改其调用的上下文指向并传送指定参数
			if (typeOf(this.options.clone) == 'function') {
				return this.options.clone.call(this, event, element, this.list);
			}
			// 剩下的是指定为复制的情况
			// 复制当前列表项并修改其样式
			var clone = element.clone(true).setStyles({
				margin: 0,
				position: 'absolute',
				visibility: 'hidden',
				width: element.getStyle('width')
			}).addEvent('mousedown', function (event) {
				element.fireEvent('mousedown', event);
			});
			//prevent the duplicated radio inputs from unchecking the real one
			if (clone.get('html').test('radio')) {
				clone.getElements('input[type=radio]').each(function (input, i) {
					input.set('name', 'clone_' + i);
					if (input.get('checked')) {
						element.getElements('input[type=radio]')[i].set('checked', true);
					}
				});
			}

			return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
		},

		// 获取可放落的对象
		getDroppables: function () {
			// 取当前列表下的所有列表项
			var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
			// 如果没有约束, 则其它列表也作为可放落对象
			if (!this.options.constrain) {
				droppables.append(this.lists).erase(this.list);
			}
			return droppables;
		},

		// 插入
		insert: function (dragging, element) {
			// 指定插入位置
			var where = 'inside';
			if (this.lists.contains(element)) {
				this.list = element;
				this.drag.droppables = this.getDroppables();
			} else {
				where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
			}
			this.element.inject(element, where);
			this.fireEvent('sort', [this.element, this.clone]);
		},

		// 开始操作
		start: function (event, element) {
			// 如果当前非闲置状态, 退出
			if (!this.idle ||
					event.rightClick ||
					['button', 'input', 'a', 'textarea'].contains(event.target.get('tag'))) {
				return;
			}

			// 设置闲置状态标识
			this.idle = false;
			// 当前操作对象
			this.element = element;
			// 获取当前透明度
			this.opacity = element.getStyle('opacity');
			// 获取当前操作对象所在的列表对象
			this.list = element.getParent();
			// 获取当前操作对象的克隆对象
			this.clone = this.getClone(event, element);

			// 创建拖放对象
			this.drag = new Drag.Move(this.clone, Object.merge({
				// 获取可放落的对象
				droppables: this.getDroppables()
			}, this.options.dragOptions)).addEvents({
				// onSnap事件监听
				onSnap: function () {
					// 停止事件冒泡及返回值
					event.stop();
					// 显示克隆对象
					this.clone.setStyle('visibility', 'visible');
					// 设置透明度
					this.element.setStyle('opacity', this.options.opacity || 0);
					// 触发onStart事件
					this.fireEvent('start', [this.element, this.clone]);
				} .bind(this),
				// 拖动进入事件
				onEnter: this.insert.bind(this),
				// 取消拖动事件
				onCancel: this.end.bind(this),
				// 拖动完成事件
				onComplete: this.end.bind(this)
			});

			// 插入克隆对象到当前对象前面
			this.clone.inject(this.element, 'before');
			// 开始拖动
			this.drag.start(event);
		},

		// 结束拖动排序
		end: function () {
			// 移除事件
			this.drag.detach();
			// 恢复透明度
			this.element.setStyle('opacity', this.opacity);
			// 如果使用形变动画(主要是外形尺寸和坐标位置上的)
			if (this.effect) {
				// 获取尺寸
				var dim = this.element.getStyles('width', 'height'),
						clone = this.clone,
						pos = clone.computePosition(this.element.getPosition(this.clone.getOffsetParent())); // 计算位置

				var destroy = function () {
					this.removeEvent('cancel', destroy);
					clone.destroy();
				};

				// 将当前克隆对象作为形变对象
				this.effect.element = clone;
				// 开始动画效果
				this.effect.start({
					top: pos.top,
					left: pos.left,
					width: dim.width,
					height: dim.height,
					opacity: 0.25
				}).addEvent('cancel', destroy).chain(destroy);
			} else {
				this.clone.destroy();
			}
			this.reset();
		},

		// 重置
		reset: function () {
			// 设置闲置状态标记
			this.idle = true;
			// 触发onComplete事件
			this.fireEvent('complete', this.element);
		},

		// 序列化
		serialize: function () {
			// 使用Array.link处理参数
			var params = Array.link(arguments, {
				modifier: Type.isFunction,
				index: function (obj) {
					return obj != null;
				}
			});
			// 遍历列表集
			var serial = this.lists.map(function (list) {
				// 遍历各列表的列表项,如果参数中传有函数的引用,使用该函数处理列表项,否则默认返回id
				return list.getChildren().map(params.modifier || function (element) {
					// 取列表项的id
					return element.get('id');
				}, this);
			}, this);

			// 参数中传送的索引值
			var index = params.index;
			// 如果列表集中只有一项,则索引值为0
			if (this.lists.length == 1) { index = 0; }
			// 如果索引存在并且值在合法范围内,返回指定索引值列表的序列化结果,否则返回整个列表集的序列化结果
			return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
		}

	});

	// #endregion

});
